﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME      : FI.Net Application Framework
// COMPONENT ID     : FI.Core.EntLib
// COMPONENT DESC   : Microsoft Enterprise Library Adapter.
//
// CREATED DATE/BY  : 18 Dec 2018 / Tian Mi
//
// REVISION HISTORY :
// DATE/BY            SR#/CS/PM#/OTHERS       DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Configuration;
using FI.Core.EntLib.Properties;
using FI.Core.IoC;
using FI.Core.Utilities;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
using Microsoft.Practices.EnterpriseLibrary.Data.Oracle;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OracleClient;
using System;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using FI.Core.SettingSource;

namespace FI.Core.Data
{
    /// <summary>
    /// A custom database factory to create <see cref="DatabaseWrapper"/>.
    /// </summary>
    [MapTo(typeof(FI.Core.Data.IDatabaseFactory), Lifetime = Lifetime.Singleton)]
    public class DatabaseWrapperFactory: FI.Core.Data.IDatabaseFactory
    {
        private static readonly DbProviderMapping defaultGenericMapping;
        //private static readonly DbProviderMapping defaultOracleMapping;
        private static readonly DbProviderMapping defaultSqlMapping;
       // private static readonly DbProviderMapping odpOracleMapping;

        static DatabaseWrapperFactory()
        {
            defaultSqlMapping       = new DbProviderMapping("System.Data.SqlClient", typeof(SqlDatabase));
           
            //defaultOracleMapping = new DbProviderMapping("System.Data.OracleClient", typeof(OracleDatabase));
            //odpOracleMapping = new DbProviderMapping("Oracle.DataAccess.Client", typeof(EntLibContrib.Data.OdpNet.OracleDatabase));
            defaultGenericMapping   = new DbProviderMapping("generic", typeof(GenericDatabase));
        }

        /// <summary>
        /// Gets the database.
        /// </summary>
        /// <param name="databaseName">Name of the connection string.</param>
        /// <returns>The <see cref="Database"/>.</returns>
        public FI.Core.Data.Database GetDatabase(string databaseName)
        {
            Guard.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            ConnectionStringsSection connectionStringSection;
            if (!ConfigManager.TryGetConfigurationSection<ConnectionStringsSection>("connectionStrings", out connectionStringSection))
            {
                throw new ConfigurationErrorsException(Resources.ExceptionNoConnectionStringSection);
            }    
            ConnectionStringSettings connectionStringSettings = connectionStringSection.ConnectionStrings[databaseName];
            ValidateConnectionStringSettings(databaseName, connectionStringSettings);
            DatabaseSettings databaseSettings;
            if (!ConfigManager.TryGetConfigurationSection<DatabaseSettings>("dataConfiguration", out databaseSettings))
            {
                databaseSettings = new DatabaseSettings();
            }
            DatabaseData databaseData = this.GetDatabaseData(connectionStringSettings, databaseSettings);
            Microsoft.Practices.EnterpriseLibrary.Data.Database database = databaseData.BuildDatabase();
            return new DatabaseWrapper(() => database, databaseName);
        }

        /// <summary>
        /// Gets the database.
        /// </summary>
        /// <returns>The <see cref="Database"/>.</returns>
        public FI.Core.Data.Database GetDatabase()
        {
            DatabaseSettings settings = ConfigManager.GetConfigurationSection<DatabaseSettings>(DatabaseSettings.SectionName);
            string databaseName = settings.DefaultDatabase;
            if (string.IsNullOrEmpty(databaseName))
            {
                throw new ConfigurationErrorsException(Resources.ExceptionDefaultDatabaseNotExists);
            }
            return this.GetDatabase(settings.DefaultDatabase);
        }

        #region Private Methods
        private static void ValidateConnectionStringSettings(string name, ConnectionStringSettings connectionStringSettings)
        {
            if (connectionStringSettings == null)
            {
                throw new ConfigurationErrorsException(Resources.ExceptionNoDatabaseDefined.Format( new object[] { name }));
            }

            if (string.IsNullOrEmpty(connectionStringSettings.ProviderName))
            {
                throw new ConfigurationErrorsException(Resources.ExceptionNoProviderDefinedForConnectionString.Format( new object[] { name }), connectionStringSettings.ElementInformation.Source, connectionStringSettings.ElementInformation.LineNumber);
            }
        }
        private static DbProviderMapping GetDefaultMapping(string dbProviderName)
        {
            if ("System.Data.SqlClient".Equals(dbProviderName))
            {
                return defaultSqlMapping;
            }
            //if ("System.Data.OracleClient".Equals(dbProviderName))
            //{
            //    return defaultOracleMapping;
            //}
            //if ("Oracle.DataAccess.Client".Equals(dbProviderName))
            //{
            //    return odpOracleMapping;
            //}
            DbProviderFactory factory = DbProviderFactories.GetFactory(dbProviderName);
            if (SqlClientFactory.Instance == factory)
            {
                return defaultSqlMapping;
            }
            //if (OracleClientFactory.Instance == factory)
            //{
            //    return defaultOracleMapping;
            //}
            return null;
        }
        private static DbProviderMapping GetGenericMapping()
        {
            return defaultGenericMapping;
        }
        private DatabaseData GetDatabaseData(ConnectionStringSettings connectionString, DatabaseSettings databaseSettings)
        {
            return CreateDatabaseData(GetAttribute(GetProviderMapping(connectionString.ProviderName, databaseSettings).DatabaseType).ConfigurationType, connectionString);
        }
        private static DatabaseData CreateDatabaseData(Type configurationElementType, ConnectionStringSettings settings)
        {
            object obj2;
            DatabaseData data;
            try
            {
                Func<string, ConfigurationSection> configurationSource = sectionName => SettingSourceFactory.GetSettingSource().GetConfigurationSection(sectionName);
                obj2 = Activator.CreateInstance(configurationElementType, new object[] { settings, configurationSource });
            }
            catch (MissingMethodException exception)
            {
                throw new InvalidOperationException(Resources.ExceptionDatabaseDataTypeDoesNotHaveRequiredConstructor.Format(new object[] { configurationElementType }), exception);
            }
            try
            {
                data = (DatabaseData)obj2;
            }
            catch (InvalidCastException exception2)
            {
                throw new InvalidOperationException(Resources.ExceptionDatabaseDataTypeDoesNotInheritFromDatabaseData.Format(new object[] { configurationElementType }), exception2);
            }
            return data;
        }
        private static DbProviderMapping GetProviderMapping(string dbProviderName, DatabaseSettings databaseSettings)
        {
            if (databaseSettings != null)
            {
                DbProviderMapping mapping = databaseSettings.ProviderMappings.Get(dbProviderName);
                if (mapping != null)
                {
                    return mapping;
                }
            }
            return (GetDefaultMapping(dbProviderName) ?? GetGenericMapping());
        }
        private static ConfigurationElementTypeAttribute GetAttribute(Type databaseType)
        {
            ConfigurationElementTypeAttribute attribute = (ConfigurationElementTypeAttribute)Attribute.GetCustomAttribute(databaseType, typeof(ConfigurationElementTypeAttribute), false);
            if (attribute == null)
            {
                throw new InvalidOperationException(Resources.ExceptionNoConfigurationElementTypeAttribute.Format(new object[] { databaseType.Name }));
            }
            return attribute;
        }
        #endregion
    }
}
